#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include <stdlib.h>


typedef struct field_byte {
	char* field;
	char* text;
	int byte;
} FIELD;

/* global variables */
int ignore_case = 0;
int from_stdin = 0;

int getString(FILE* file, char** linea, int delimiter, int* eof){
	int ch, i, tam;
	
	i=0;
	tam = sizeof(char);
	*linea = (char*)malloc(50*tam);
	if (!(*linea)) return 1;
	memset(*linea,0,50*tam);
	while (((ch = fgetc(file)) != EOF)&&(ch != delimiter)){
			(*linea)[i++]= (char)ch;
			*linea = realloc(*linea, 50+(++tam));
			if (!(*linea)) return 1;
	}
	if (ch == EOF)
		*eof = 1;
	return 0;
}

/* auxiliar function */
void to_lower(char** source){
	int i;
	int l;
	l = strlen(*source);
	for (i=0; i<l; i++){
		(*source)[i] = tolower((*source)[i]);
	}
}

/* Free memory alloc'd for structs and strings */
void free_fields(FIELD* campos, int cant_campos, int from_stdin){
	int i;
	for (i=0; i<cant_campos; i++){
		free(campos[i].field);
		if (from_stdin)
			free(campos[i].text);
	}
	if (!from_stdin)
		free(campos[i].field);/* free the last one */
	free(campos);
}


void showAyuda() {
	printf("Usage: join [OPTION]... FILE1 FILE2\n");
	printf("For each pair of input lines with identical join fields, write a line to\n");
	printf("standar output. The default join field is the first, delimited\n");
	printf("by whitespace. When FILE1 or FILE2 (not both) is -, read standard input.\n\n");

    printf("-i, --ignore-case ignore differences in case when comparing fields\n");
    printf("-h. --help        display this help and exit\n");
    printf("-v, --version     output version information an exit\n\n");
	
	printf("Important: FILE1 and FILE2 must be sorted on the join fields.\n");
	printf("E.g., use 'sort -d 1b,1' if 'join' has no options.\n");
	printf("Note, comparisons honor the rules specified by 'LC_COLLATE'.\n");
	printf("If the input is not sorted and some lines cannot be joined, a\n");
	printf("warning message will be given.\n");
}

void showVersion() {
	printf("join -- version 1.0\n");
}

int join(char* source, char* target) {
	FILE *f = NULL;
	FILE *in = NULL;
	char* aux = NULL;
	char verify_order[500];
	char* field_in = NULL;
	char* text_in = NULL;
	FIELD* campos = NULL;
	int eof = 0;
	int eof_input = 0;
	int cant_campos = 0;
	int i, tam, join;

	/* Ascii de los caracteres delimitadores de strings */
	/* 
	 * 10: enter
	 * 32: blank space
	 */
	
	f = (from_stdin)?stdin:fopen(target, "rt");
	if (!f) return -1;
	tam = sizeof(FIELD);
	campos = (FIELD*)malloc(tam);
	strcpy(verify_order, "");
	while (!eof){
		getString(f, &campos[cant_campos].field, 32, &eof);
		if (ignore_case)
			to_lower(&campos[cant_campos].field);
		if (!eof)
			if (strcmp(verify_order, campos[cant_campos].field) == 1) {
				fprintf(stderr, "-- error 01: El archivo [%s] no está ordenado\n", target);
				free_fields(campos, cant_campos, from_stdin);
				if (in) fclose(in);
				if ((!from_stdin)&&(f)) fclose(f);
				return 1;
			}
		if (from_stdin) {
			getString(f, &campos[cant_campos].text, 10, &eof);
		} 
		if (!from_stdin) {
			campos[cant_campos].byte =ftell(f);
			getString(f, &aux, 10, &eof);
			free(aux);
			if (eof) 
				cant_campos--;
		}
		campos = realloc(campos, tam+=(sizeof(FIELD)));
		strcpy(verify_order, campos[cant_campos].field);
		cant_campos++;
	}
	
	/* Processing  file 1 */
	in = fopen(source, "rt");
	if (!in) return -1;
	
	while (!eof_input){
		getString(in, &field_in, 32, &eof_input);
		if (ignore_case)
			to_lower(&field_in);
		join = 0;
		for(i=0; i<cant_campos; i++){
			if (strcmp(field_in, campos[i].field) == 0) {
				printf("%s ", field_in);
				if (!join)  getString(in, &text_in, 10, &eof_input);
				printf("%s ", text_in);
				if (!from_stdin){	
					fseek(f, campos[i].byte, SEEK_SET);
					getString(f, &aux, 10, &eof_input);
					printf("%s\n", aux);
					free(aux);
				} else {
					printf("%s\n", campos[i].text);
				}
				join = 1;
			} else {				
			/* no esta el campo o ya lo pase */
				if (strcmp(field_in, campos[i].field) == -1) {
					if (!join) {
						getString(in, &text_in, 10, &eof_input);
						if (!eof_input)
							fprintf(stderr, "-- error 02: El campo [%s] no tiene correspondencia en el archivo [%s]\n", field_in, target);
					}
					break;
						/* imprimir eventual error. Usar boolean para ver
						* si hubo valor antes*/
				}
			}
		} /* end - for */
		
		/* All values in the array are lower than target field
		 * Process is over.
		 */
		if ((i == cant_campos) && (!join)) {
			if (!eof_input){
				fprintf(stderr, "-- error 02: El campo [%s] no tiene correspondencia en el archivo [%s]\n", field_in, target);
				getString(in, &text_in, 10, &eof_input);
			}
		}
		if (field_in){
			free(field_in);
			field_in = NULL;
		}
		if (text_in){
			free(text_in);
			text_in = NULL;
		}
	} /* end- while */
	
	/* Close files */
	if (in) fclose(in);
	if ((!from_stdin)&&(f)) fclose(f);
	
	/* Free memory */
	free_fields(campos, cant_campos, from_stdin);
	
	return 0;
}


int main(int argc, char* argv[]){
	
	int siguiente_opcion = 0;
	int option_index = 0;
	char* input_file = NULL;
	
	while (1) {
	/* Una estructura de varios arrays describiendo los valores largos */
	static struct option op_largas[] =
	{	//name, has_arg, flag, valor
	    { "version", no_argument, 0,  'v'},
        { "help", no_argument, 0,  'h'},
 	    { "ignore-case", required_argument, 0, 'i'},
	    { 0, 0, 0, 0 }
	};
	
    /* Llamamos a la funcion getopt */
    siguiente_opcion = getopt_long (argc, argv, "i:vh", op_largas, &option_index);

	if (siguiente_opcion == -1)
      break;

    switch (siguiente_opcion) {
		case 'h' : /* -h o --help */
				showAyuda();
				break;
		case 'v' : /* -v o --version */
                 showVersion();
		        break;
		case 'i' : /* -i o --ignore-case */
		        ignore_case = 1;
		        input_file = optarg;
                break;
		case '?' : /* opcion no valida */
				fprintf(stderr, "Invalid parameter/s\n");
		       	break; 
		default : /* Algo mas? No esperado. Abortamos */
		        abort();
       }
	}
	argv = argv+optind;
	if (input_file){
		if (!(*argv)) {
			from_stdin = 1;
			return join(input_file, "stdin");
		}
		return join(input_file, *argv);
	} else {
		if (!(*(argv+1))) {
			from_stdin = 1;
			return join(*argv, "stdin");
		}
		return join(*argv, *(argv+1));
	}
	return 0;
}
